package com.iwhalecloud.bss.kite.cucc.common.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;


import com.iwhalecloud.bss.kite.cart.common.util.KiteLogProducerUtil;
import com.iwhalecloud.bss.kite.common.cache.AbstractSecondLevelCache;
import com.iwhalecloud.bss.kite.common.util.KiteBeanUtils;
import com.iwhalecloud.bss.kite.log.client.dto.InfKiteServiceLogReqDTO;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.iwhalecloud.bss.kite.client.api.inf.IInfKiteServiceLogService;
import com.iwhalecloud.bss.kite.client.dto.inf.InfKiteServiceLogDTO;
import com.iwhalecloud.bss.kite.common.constant.ErrorConsts;
import com.iwhalecloud.bss.kite.common.constant.KeyConsts;
import com.iwhalecloud.bss.kite.common.constant.TableConsts;
import com.iwhalecloud.bss.kite.common.util.GrayUtil;
import com.iwhalecloud.bss.kite.common.util.JsonUtils;
import com.iwhalecloud.bss.kite.common.util.KiteDateUtils;
import com.iwhalecloud.bss.kite.common.util.KiteMapUtils;
import com.iwhalecloud.bss.kite.common.util.KiteStringUtils;
import com.iwhalecloud.bss.kite.common.util.SeqUtils;
import com.iwhalecloud.bss.kite.cucc.common.constant.LocalInfConsts;

import com.ztesoft.bss.common.util.ContextUtil;
import com.ztesoft.bss.common.util.SpringUtil;
import com.ztesoft.common.logger.api.ZLogger;
import com.ztesoft.common.logger.factory.ZLoggerFactory;

public class KiteHttpsUtils {

     private static final ZLogger LOGGER = ZLoggerFactory.getLogger(KiteHttpsUtils.class, KeyConsts.LOG_MODULE);

    /**
     * get
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @return
     * @throws Exception
     */
    public static String doGet(String host, String path, Map<String, String> headers, Map<String, String> paramMap) {
        InfKiteServiceLogDTO logDto = initLog(path, JSONObject.toJSONString(paramMap));
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpGet request = new HttpGet(buildUrl(host, path, paramMap));
            GrayUtil.addHeaderForGray(request);
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            HttpResponse resp = httpClient.execute(request);

            String respStr = getResult(resp);

            logDto.setOutXml(respStr);
            return respStr;
        }
        catch (Exception e) {
            logDto.setOutXml(e.getMessage());
        }
        finally {
            addInfServiceLog(logDto);
        }
        return null;
    }

    /**
     * 不加灰度信息
     * @param host
     * @param path
     * @param headers
     * @param paramMap
     * @return
     */
    public static String doGetNonGray(String host, String path, Map<String, String> headers, Map<String, String> paramMap) {
        InfKiteServiceLogDTO logDto = initLog(path, JSONObject.toJSONString(paramMap));
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpGet request = new HttpGet(buildUrl(host, path, paramMap));
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            HttpResponse resp = httpClient.execute(request);

            String respStr = getResult(resp);

            logDto.setOutXml(respStr);
            return respStr;
        }
        catch (Exception e) {
            logDto.setOutXml(e.getMessage());
        }
        finally {
            addInfServiceLog(logDto);
        }
        return null;
    }

    /**
     * post form
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @param bodys
     * @return
     * @throws Exception
     */
    public static String doPost(String host, String path, Map<String, String> headers, Map<String, String> paramMap, Map<String, String> bodys) {
        InfKiteServiceLogDTO logDto = initLog(path, JSONObject.toJSONString(paramMap));
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpPost request = new HttpPost(buildUrl(host, path, bodys));
            GrayUtil.addHeaderForGray(request);
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            if (!KiteMapUtils.isEmpty(paramMap)) {
                request.setHeader("Content-type", "application/json");
                StringEntity postingString = new StringEntity(JSON.toJSONString(paramMap), "UTF-8");
                request.setEntity(postingString);
            }
            HttpResponse resp = httpClient.execute(request);
            String respStr = getResult(resp);
            logDto.setOutXml(respStr);
            return respStr;
        }
        catch (Exception e) {
            logDto.setOutXml(e.getMessage());
        }
        finally {
            addInfServiceLog(logDto);
        }
        return null;
    }

    /**
     * post form
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @param bodys
     * @return
     * @throws Exception
     */
    public static String doPostByGetParams(String host, String path, Map<String, String> headers, Map<String, String> paramMap, Map<String, String> bodys) {
        InfKiteServiceLogDTO logDto = initLog(path, JSONObject.toJSONString(paramMap));
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpPost request = new HttpPost(buildUrl(host, path, paramMap));
            GrayUtil.addHeaderForGray(request);
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            HttpResponse resp = httpClient.execute(request);
            String respStr = getResult(resp);
            logDto.setOutXml(respStr);
            return respStr;
        }
        catch (Exception e) {
            logDto.setOutXml(e.getMessage());
        }
        finally {
            addInfServiceLog(logDto);
        }
        return null;
    }

    public static String doPostWithObject(String host, String path, Map<String, String> headers, Map<String, Object> paramMap, Map<String, String> bodys) {
        InfKiteServiceLogDTO logDto = initLog(path, JSONObject.toJSONString(paramMap));
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpPost request = new HttpPost(buildUrl(host, path, bodys));
            GrayUtil.addHeaderForGray(request);
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            if (!KiteMapUtils.isEmpty(paramMap)) {
                request.setHeader("Content-type", "application/json");
                StringEntity postingString = new StringEntity(JSON.toJSONString(paramMap), "UTF-8");
                request.setEntity(postingString);
            }
            HttpResponse resp = httpClient.execute(request);
            String respStr = getResult(resp);
            logDto.setOutXml(respStr);
            return respStr;
        } catch (Exception e) {
            logDto.setOutXml(e.getMessage());
        } finally {
            addInfServiceLog(logDto);
        }
        return null;
    }

    public static String doPostWithObjectWithoutLog(String host, String path, Map<String, String> headers, Map<String, Object> paramMap, Map<String, String> bodys) {
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpPost request = new HttpPost(buildUrl(host, path, bodys));
            GrayUtil.addHeaderForGray(request);
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            if (!KiteMapUtils.isEmpty(paramMap)) {
                request.setHeader("Content-type", "application/json");
                StringEntity postingString = new StringEntity(JSON.toJSONString(paramMap), "UTF-8");
                request.setEntity(postingString);
            }
            HttpResponse resp = httpClient.execute(request);
            String respStr = getResult(resp);
            return respStr;
        } catch (Exception e) {
            LOGGER.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, ErrorConsts.SYSTEM_ERROR.getMsg()); // e.printStackTrace();
        }
        return null;
    }


    /**
     * post form
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @param bodys
     * @return
     * @throws Exception
     */
    public static String doPostObjMap(String host, String path, Map<String, String> headers, Map<String, Object> paramMap, Map<String, String> bodys) {
        InfKiteServiceLogDTO logDto = initLog(path, JSONObject.toJSONString(paramMap));
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpPost request = new HttpPost(buildUrl(host, path, bodys));
            GrayUtil.addHeaderForGray(request);
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            if (!KiteMapUtils.isEmpty(paramMap)) {
                request.setHeader("Content-type", "application/json");
                StringEntity postingString = new StringEntity(JSON.toJSONString(paramMap), "UTF-8");
                request.setEntity(postingString);
            }
            HttpResponse resp = httpClient.execute(request);
            String respStr = getResult(resp);
            logDto.setOutXml(respStr);
            return respStr;
        }
        catch (Exception e) {
            logDto.setOutXml(e.getMessage());
        }
        finally {
            addInfServiceLog(logDto);
        }
        return null;
    }

    /**
     * Post String
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, Map<String, String> headers, Map<String, String> paramMap,
            String body) throws Exception {
        HttpClient httpClient = wrapClient(host, path);
        HttpPost request = new HttpPost(buildUrl(host, path, paramMap));
        GrayUtil.addHeaderForGray(request);
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }
        return httpClient.execute(request);
    }

    /**
     * Post stream
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, Map<String, String> headers, Map<String, String> paramMap,
            byte[] body) throws Exception {
        HttpClient httpClient = wrapClient(host, path);
        HttpPost request = new HttpPost(buildUrl(host, path, paramMap));
        GrayUtil.addHeaderForGray(request);
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }
        return httpClient.execute(request);
    }

    /**
     * Put String
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, Map<String, String> headers, Map<String, String> paramMap,
            String body) throws Exception {
        HttpClient httpClient = wrapClient(host, path);
        HttpPut request = new HttpPut(buildUrl(host, path, paramMap));
        GrayUtil.addHeaderForGray(request);
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }
        return httpClient.execute(request);
    }

    /**
     * Put stream
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, Map<String, String> headers, Map<String, String> paramMap,
            byte[] body) throws Exception {
        HttpClient httpClient = wrapClient(host, path);
        HttpPut request = new HttpPut(buildUrl(host, path, paramMap));
        GrayUtil.addHeaderForGray(request);
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }
        return httpClient.execute(request);
    }

    /**
     * Delete
     *
     * @param host
     * @param path
     *
     * @param headers
     * @param paramMap
     * @return
     * @throws Exception
     */
    public static HttpResponse doDelete(String host, String path, Map<String, String> headers,
            Map<String, String> paramMap) throws Exception {
        HttpClient httpClient = wrapClient(host, path);
        HttpDelete request = new HttpDelete(buildUrl(host, path, paramMap));
        GrayUtil.addHeaderForGray(request);
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        return httpClient.execute(request);
    }

    /**
     * 构建请求的 url
     *
     * @param host
     * @param path
     * @param paramMap
     * @return
     * @throws UnsupportedEncodingException
     */
    private static String buildUrl(String host, String path, Map<String, String> paramMap)
            throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        if (!StringUtils.isBlank(host)) {
            sbUrl.append(host);
        }
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != paramMap) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : paramMap.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }
        return sbUrl.toString();
    }

    /**
     * 获取 HttpClient
     *
     * @param host
     * @param path
     * @return
     */
    private static HttpClient wrapClient(String host, String path) {
        HttpClient httpClient = HttpClientBuilder.create().build();
        if (host != null && host.startsWith("https://")) {
            return sslClient();
        }
        else if (StringUtils.isBlank(host) && path != null && path.startsWith("https://")) {
            return sslClient();
        }
        return httpClient;
    }

    /**
     * 在调用SSL之前需要重写验证方法，取消检测SSL 创建ConnectionManager，添加Connection配置信息
     *
     * @return HttpClient 支持https
     */
    private static HttpClient sslClient() {
        try {
            // 在调用SSL之前需要重写验证方法，取消检测SSL
            X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] xcs, String str) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] xcs, String str) {
                }
            };
            SSLContext ctx = SSLContext.getInstance(SSLConnectionSocketFactory.TLS);
            ctx.init(null, new TrustManager[] {trustManager}, null);
            SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(ctx,
                    NoopHostnameVerifier.INSTANCE);
            // 创建Registry
            RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD_STRICT)
                    .setExpectContinueEnabled(Boolean.TRUE)
                    .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
                    .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)).build();
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.INSTANCE).register("https", socketFactory).build();
            // 创建ConnectionManager，添加Connection配置信息
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
                    socketFactoryRegistry);
            CloseableHttpClient closeableHttpClient = HttpClients.custom().setConnectionManager(connectionManager)
                    .setDefaultRequestConfig(requestConfig).build();
            return closeableHttpClient;
        }
        catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        }
        catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 将结果转换成JSONObject
     *
     * @param httpResponse
     * @return
     * @throws IOException
     */
    public static String getResult(HttpResponse httpResponse) throws IOException {
        if (httpResponse.getStatusLine().getStatusCode() != 200) {
            ErrorConsts.INF_INVOKE_EXCEPTION.throwOut("",httpResponse.getStatusLine().getReasonPhrase());
        }
        InputStream inputStream = httpResponse.getEntity().getContent();
        String result = new BufferedReader(new InputStreamReader(inputStream))
                .lines().collect(Collectors.joining(System.lineSeparator()));
        return result;
    }

    private static InfKiteServiceLogDTO initLog(String svcCode, String reqParam) {
        InfKiteServiceLogDTO logDto = new InfKiteServiceLogDTO();
        logDto.setLogId(SeqUtils.getNext(TableConsts.InfKiteServiceLog));
        logDto.setSvcCode(svcCode);
        logDto.setAppKey("KITE");
        logDto.setReqTime(KiteDateUtils.dateToLocalDateTime(new Date()));
        logDto.setInXml(reqParam);
        logDto.setTransactionId(ContextUtil.getHttpSessoinId());
        return logDto;
    }

    private static void addInfServiceLog(InfKiteServiceLogDTO logDto) {
         // 异步记录日志
        CompletableFuture.runAsync(() -> {
            try {
                logDto.setRspTime(KiteDateUtils.dateToLocalDateTime(new Date()));
                //解决冲突
                AbstractSecondLevelCache dcSystemParamCache = SpringUtil.getBean("dcSystemParamCache", AbstractSecondLevelCache.class);
                if (dcSystemParamCache != null && "0".equals(KiteMapUtils.beanToMap(dcSystemParamCache.defaultGetObject("INF_KITE_SERVICE_LOG_BY_MQ")).get("paramVal"))) {
                    IInfKiteServiceLogService service = SpringUtil.getBean(IInfKiteServiceLogService.class);
                    service.addInfKiteServiceLog(logDto);
                }
                else {
                    // 日志走mq
                    InfKiteServiceLogReqDTO serviceLogReqDTO = KiteBeanUtils.transform(logDto, InfKiteServiceLogReqDTO.class);
                    KiteLogProducerUtil.addInfKiteServiceLogSync(serviceLogReqDTO);
                }
            }
            catch (Exception e) {
                // just log
                LOGGER.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, ErrorConsts.SYSTEM_ERROR.getMsg());
            }

        });

        /*logDto.setRspTime(KiteDateUtils.dateToLocalDateTime(new Date()));

        IInfKiteServiceLogService service = SpringUtil.getBean(IInfKiteServiceLogService.class);
        service.addInfKiteServiceLog(logDto);*/

    }

    public static String doPostWithObjectSetLog(String host, String path, Map<String, String> headers, Map<String, Object> paramMap, Map<String, String> bodys) {
        InfKiteServiceLogDTO logDto = new InfKiteServiceLogDTO();
        logDto.setLogId(SeqUtils.getNext(TableConsts.InfKiteServiceLog));
        logDto.setSvcCode(path);
        logDto.setAppKey("KITE");
        logDto.setReqTime(KiteDateUtils.dateToLocalDateTime(new Date()));
        logDto.setInXml(JSONObject.toJSONString(paramMap));
        Map<String, Object> uniBssHead = KiteMapUtils.getValue(paramMap, "UNI_BSS_HEAD");
        String transId = KiteMapUtils.getString(uniBssHead, "TRANS_ID");
        if (KiteStringUtils.isEmpty(transId)) {
            transId = ContextUtil.getHttpSessoinId();
        }
        logDto.setTransactionId(transId);
        try {
            HttpClient httpClient = wrapClient(host, path);
            HttpPost request = new HttpPost(buildUrl(host, path, bodys));
            GrayUtil.addHeaderForGray(request);
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            if (!KiteMapUtils.isEmpty(paramMap)) {
                request.setHeader("Content-type", "application/json");
                StringEntity postingString = new StringEntity(JSON.toJSONString(paramMap), "UTF-8");
                request.setEntity(postingString);
            }
            HttpResponse resp = httpClient.execute(request);
            String respStr = getResult(resp);
            logDto.setOutXml(respStr);
            Map<String, Object> respMap = JsonUtils.parseObject(respStr, Map.class);
            Map<String, Object> uniBssBody = KiteMapUtils.getValue(respMap, "UNI_BSS_BODY");
            Map<String, Object> pathRsp = KiteMapUtils.getValue(uniBssBody, "PATH_VARIABLE_RSP");
            logDto.setResultCode(KiteMapUtils.getString(pathRsp, "STATUS"));
            logDto.setResultMsg(KiteMapUtils.getString(pathRsp, "MESSAGE"));
            if (KiteStringUtils.equals(LocalInfConsts.UNI_BSS_BODY_RESULT_SUCCESS, KiteMapUtils.getString(pathRsp, "STATUS"))) {
                logDto.setCustOrderId(KiteMapUtils.getString(pathRsp, "DATA"));
            }
            return respStr;
        } catch (Exception e) {
            logDto.setOutXml(e.getMessage());
        } finally {
            addInfServiceLog(logDto);
        }
        return null;
    }
}
